﻿using System;

namespace tdgame {
	public static class PerlinNoise {

		public enum InterpolationMode {
			None,
			Linear,
			Cosine
		}

		/// <summary>
		/// Perlin noise generator
		/// </summary>
		/// <param name="width">Width of the output map</param>
		/// <param name="height">Height of the output map</param>
		/// <param name="min">Minimum possible value</param>
		/// <param name="max">Maximum possible value</param>
		/// <param name="persistence">Amplitude persistance between octaves</param>
		/// <param name="maxOctaves">Max number of octaves to run. Algorithm will automatically stop when it reaches pixel-level detail.</param>
		/// <param name="mode">Interpolation mode</param>
		/// <param name="blur">To blur or not to blur</param>
		/// <param name="seed">Map seed</param>
		/// <returns>A 2D heightmap generated by Perlin noise</returns>
		public static double[,] GetPerlinNoise(int width, int height, double min, double max, double persistence, int maxOctaves, InterpolationMode mode, bool blur, int seed) {
            double[,] map = new double[width, height];
            Random R = new Random(seed);

            int frequency = 1,
                count = 0;
            double amplitude = (max - min) / 2.0,
				maxHeight = 0.0, 
				minHeight = maxOctaves;

			while ((frequency / 2) <= width && (frequency / 2) <= height && count < maxOctaves) {

				//create noise
				double[,] noise = new double[frequency + 1, frequency + 1];
				for (int x = 0; x <= frequency; x++) {
					for (int y = 0; y <= frequency; y++) {
						double n = (R.NextDouble() * amplitude * (R.Next(2) == 0 ? -1 : 1)) + (min + amplitude);
						noise[x, y] = n;
					}
				}

				double xmod = ((double)width / (double)frequency),
					ymod = ((double)height / (double)frequency);

				for (int x = 0; x < width; x++) {
					for (int y = 0; y < height; y++) {
						int noisex = (int)((double)x / xmod),
							noisey = (int)((double)y / ymod);

						double xoffset = ((double)x % xmod) / xmod,
							yoffset = ((double)y % ymod) / ymod;

						switch (mode) {
							case InterpolationMode.None:
								map[x, y] += noise[noisex, noisey];
								break;
							case InterpolationMode.Linear:
								double interp1 = noise[noisex, noisey] + ((noise[noisex + 1, noisey] - noise[noisex, noisey]) * xoffset),
									interp2 = noise[noisex, noisey + 1] + ((noise[noisex + 1, noisey + 1] - noise[noisex, noisey + 1]) * xoffset);
								map[x, y] += interp1 + ((interp2 - interp1) * yoffset);
								break;
							case InterpolationMode.Cosine:
								double xinterp1 = Interpolate(noise[noisex, noisey], noise[noisex + 1, noisey], xoffset),
									xinterp2 = Interpolate(noise[noisex, noisey + 1], noise[noisex + 1, noisey + 1], xoffset);
								map[x, y] += Interpolate(xinterp1, xinterp2, yoffset);
								break;
						}
					}
				}
				amplitude *= persistence;
				count++;
				frequency *= 2;
			}

			if (blur) {
				double[,] blurred = new double[width, height];
				for (int x = 0; x < width; x++) {
					for (int y = 0; y < height; y++) {
						double tot = map[x, y];
						int ct = 1;

						if (x > 0) {
							tot += map[x - 1, y];
							ct++;
						}
						if (x < (width - 1)) {
							tot += map[x + 1, y];
							ct++;
						}
						if (y > 0) {
							tot += map[x, y - 1];
							ct++;
						}
						if (y < (height - 1)) {
							tot += map[x, y + 1];
							ct++;
						}
						if (x > 0 && y > 0) {
							tot += map[x - 1, y - 1];
							ct++;
						}
						if (x > 0 && y < (height - 1)) {
							tot += map[x - 1, y + 1];
							ct++;
						}
						if (x < (width - 1) && y > 0) {
							tot += map[x + 1, y - 1];
							ct++;
						}
						if (x < (width - 1) && y < (height - 1)) {
							tot += map[x + 1, y + 1];
							ct++;
						}
						blurred[x, y] = tot / (double)ct;
					}
				}
				map = blurred;
			}

			//locate min and max values
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    double n = map[x, y];
                    if (n > maxHeight) maxHeight = n;
                    if (n < minHeight) minHeight = n;
                }
            }

			//normalize to the specified min/max range
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
					map[x, y] = ((map[x, y] - minHeight) / (maxHeight - minHeight)) * (max - min) + min;
                }
            }
            return map;
        }

		static double Interpolate(double v1, double v2, double x) {
			x = x * Math.PI;
			double f = (1.0 - Math.Cos(x)) * 0.5;
			return v1 * (1.0 - f) + (v2 * f);
		}
	}

}
